package com.foodev.maven.gwt;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.FileUtils;

/**
 * GWT compiles modules. Checks dependencies' timestamps to ensure compilation
 * should be done.
 * 
 * @goal compile
 * @phase compile
 * @requiresDependencyResolution compile
 * @author Martin Algesten
 */
public class CompileMojo extends AbstractGwtScriptMojo {

    /**
     * Logs output in a graphical tree view. Defaults to false
     * 
     * @parameter expression=false
     */
    private boolean treeLogger;

    /**
     * Validate all source code, but do not compile. Defaults to false.
     * 
     * @parameter expression=fale
     */
    private boolean validateOnly;

    
    public final static String SKIP_SYSTEM_ENV = "maven.xgwtcompile.skip";

    
    protected boolean skipCompile() {

        String s = System.getenv(SKIP_SYSTEM_ENV);

        if ( s == null ) s = System.getProperty(SKIP_SYSTEM_ENV, null);
        
        return s != null && s.equalsIgnoreCase("true");
        
    }

    
    public void execute() throws MojoExecutionException, MojoFailureException {

        try {

            if ( skipCompile() ) {
                getLog().info("GWT compilation is skipped.");
                return;
            }
            
            File file = createScriptFile("compile");
            FileWriter fwriter = new FileWriter(file);
            PrintWriter writer = new PrintWriter(fwriter);

            List<String> rawClasspath = buildClasspath();

            List<String> depClasspath = filterClasspath(rawClasspath, true);

            // compilation should filter out the 'target/classes'
            List<String> classpath = filterClasspath(buildClasspath(), false);

            startScript(writer);

            writeClasspath(writer, classpath);

            writeCommand(writer);

            writer.close();
            fwriter.close();

            long latestDependentTimestamp = getLatestDependentTimestamp(depClasspath);

            long latestOutputTimestamp = getLatestOutputTimestamp(modules);

            if (latestDependentTimestamp > latestOutputTimestamp) {

                int exitValue = executeProcess(gwtHome, new String[] { file
                        .getAbsolutePath() });

                if (exitValue != 0)
                    throw new MojoExecutionException("GWT compilation failed."
                            + " Exit value: " + exitValue);

                cleanUpAfterCompile();

            } else {

                getLog()
                        .info("Nothing to compile - GWT modules are up to date");

            }

        } catch (MojoExecutionException e) {

            throw e;

        } catch (Exception e) {

            getLog().error(e);

            throw new MojoFailureException("GWT compilation failed.");

        }

    }


    /**
     * Writes out the GWT compilation line into the script.
     * 
     * @param writer The writer to write to.
     * @throws IOException
     */
    private void writeCommand(PrintWriter writer) throws IOException {

        writeJavaCommand(writer);

        writeSharedVMArgs(writer);

        writer.write(" com.google.gwt.dev.Compiler");

        writeSharedArgs(writer, webappSrc);

        if (treeLogger)
            writer.write(" -treeLogger");

        if (validateOnly)
            writer.write(" -validateOnly");

        writeModules(writer);

    }


    /**
     * Finds the latest timestamp in the given dependencies classpath. Note that
     * the dependencies includes the source directory.
     * 
     * @param classpath the classpath
     * @return The latest timestamp of the classpath entries, directories are
     *         recursively checked.
     */
    private long getLatestDependentTimestamp(List<String> classpath) {

        long latest = 0;

        for (String element : classpath) {

            File f = new File(element);

            long itemLatest = findLatest(f, 0);

            if (itemLatest > latest)
                latest = itemLatest;

        }

        return latest;

    }


    /**
     * Checks the latest timestamp of the output modules in target.
     * 
     * @param modules The GWT module to check.
     * @return
     */
    private long getLatestOutputTimestamp(List<String> modules) {

        long latest = 0;

        for (String module : modules) {

            File f = new File(webappSrc, module);

            long itemLatest = findLatest(f, 0);

            if (itemLatest > latest)
                latest = itemLatest;

        }

        return latest;

    }


    /**
     * Recursive helper method that finds the latest timestamp for a
     * file/directory.
     * 
     * @param file the file to check
     * @param latest set to 0 for start of recursion.
     * @return the latest timestamp of the file/directory.
     */
    private long findLatest(File file, long latest) {

        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                latest = findLatest(files[i], latest);
            }
        }

        long lastMod = file.lastModified();

        return lastMod > latest ? lastMod : latest;

    }


    /**
     * Removes the .gwt-tmp directory in the output and moves the -aux
     * directories to <code>target/gwt</code>
     * 
     * @param modules the list of modules.
     * @throws IOException
     */
    private void cleanUpAfterCompile() throws IOException {

        File remove = new File(webappSrc, ".gwt-tmp");

        FileUtils.deleteDirectory(remove);

        for (String module : modules) {

            File auxDir = new File(webappSrc, module + "-aux");

            if (auxDir.exists()) {

                File moveTo = new File(gwtHome, module + "-aux");

                if (moveTo.exists())
                    FileUtils.deleteDirectory(moveTo);

                auxDir.renameTo(moveTo);

            }

        }

    }

}
